package org.eredlab.g4.rif.report.excel;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import jxl.Cell;
import jxl.Workbook;
import jxl.write.Label;
import jxl.write.Number;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eredlab.g4.ccl.datastructure.Dto;
import org.eredlab.g4.ccl.datastructure.impl.BasePo;
import org.eredlab.g4.ccl.datastructure.impl.BaseDto;
import org.eredlab.g4.ccl.datastructure.impl.BaseVo;
import org.eredlab.g4.ccl.util.G4Constants;
import org.eredlab.g4.ccl.util.G4Utils;

/**
 * Excel数据填充器
 * 
 * @author XiongChun
 * @since 2010-08-12
 */
public class ExcelFiller {

	private Log log = LogFactory.getLog(ExcelFiller.class);
	//包含了需要填充的模板
	private ExcelTemplate excelTemplate = null;
	//包含了具体要填充的值！
	private ExcelData excelData = null;

	public ExcelFiller() {
	}

	/**
	 * 构造函数
	 * 
	 * @param pExcelTemplate
	 * @param pExcelData
	 */
	public ExcelFiller(ExcelTemplate pExcelTemplate, ExcelData pExcelData) {
		setExcelData(pExcelData);
		setExcelTemplate(pExcelTemplate);
	}

	/**
	 * 数据填充 将ExcelData填入excel模板，其实也可以理解成替代模板中对应的参数！然后将写进去的所有数据以字节流数组的方式返回！
	 * 
	 * @return ByteArrayOutputStream
	 */
	public ByteArrayOutputStream fill(HttpServletRequest request) {
		WritableSheet wSheet = null;
		//用于装载所有写入的数据！
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try 
		{
			//对模板文件创建一个输入流！
	        InputStream is = request.getSession().getServletContext().getResourceAsStream(getExcelTemplate().getTemplatePath()); 
	        //对指定的流文件创建一个工作薄
			Workbook wb = Workbook.getWorkbook(is);
			//以现有的工作薄为模板创建一个使其可以被修改的工作薄！
			WritableWorkbook wwb = Workbook.createWorkbook(bos, wb);
			//得到第一页！
			wSheet = wwb.getSheet(0);
			System.out.println("正在替换模板参数！");
			//写入静态值，这个方法其实完全可以不调用，不调用其实还好些，覆盖然后又填充上以前的值完全多此一举！
			//fillStatics(wSheet);
			//替换占位符
			fillParameters(wSheet);
			fillFields(wSheet);
			System.out.println("替换模板参数完成！");
			wwb.write();
			wwb.close();
			wb.close();
		} 
		catch (Exception e) 
		{
			log.error(G4Constants.Exception_Head + "基于模板生成可写工作表出错了!");
			e.printStackTrace();
		}
		return bos;
	}

	/**
	 * 写入静态对象
	 */
	private void fillStatics(WritableSheet wSheet) 
	{
		//获得装载静态值的集合
		List statics = getExcelTemplate().getStaticObject();
		for (int i = 0; i < statics.size(); i++) 
		{
			//取出静态值的单元格对象!
			Cell cell = (Cell) statics.get(i);
			//通过指定单元格的坐标（行和列），然后在指定的单元格上写上静态的文字！
			Label label = new Label(cell.getColumn(), cell.getRow(), cell.getContents());
			//设置标题的字体，自己就用摹本中的原始字体格式！
			label.setCellFormat(cell.getCellFormat());
			try 
			{
				//添加一个单元格，添加时是通过坐标来添加的而不是按顺序依次添加！
				wSheet.addCell(label);
			} 
			catch (Exception e) 
			{
				log.error(G4Constants.Exception_Head + "写入静态对象发生错误!");
				e.printStackTrace();
			}
		}
	}

	/**
	 * 写入参数对象
	 */
	private void fillParameters(WritableSheet wSheet)
	{
		//得到装载带有$p前缀的单元格集合！
		List parameters = getExcelTemplate().getParameterObjct();
		//得到对应的值！      里面的键对应的就是占位符的名称！
		Dto parameterDto = getExcelData().getParametersDto();
		//有多深占位符就循环多少次，同下面的字段填充是有区别的，字段填充的循环是根据哟多少条记录来确定的！
		for (int i = 0; i < parameters.size(); i++) 
		{
			//得到当前单元格对象！
			Cell cell = (Cell) parameters.get(i);
			//得当当前单元格中的占位参数名，以及单元格的值
			String key = getKey(cell.getContents().trim());
			String type = getType(cell.getContents().trim());
			try 
			{
				//不同的数据类型使用不同的格式！ 当前为数字类型！每一种类型的构造函数都包含了一个行和一列的坐标参数！然后紧跟着是值，如果要格式化，还可以在后面跟上格式化对象！
				if (type.equalsIgnoreCase(G4Constants.ExcelTPL_DataType_Number)) 
				{
					Number number = new Number(cell.getColumn(), cell.getRow(), parameterDto.getAsBigDecimal(key).doubleValue());
					number.setCellFormat(cell.getCellFormat());
					wSheet.addCell(number);
				} 
				else 
				{
					Label label = new Label(cell.getColumn(), cell.getRow(), parameterDto.getAsString(key));
					label.setCellFormat(cell.getCellFormat());
					wSheet.addCell(label);
				}
			}
			catch (Exception e)
			{
				log.error(G4Constants.Exception_Head + "写入表格参数对象发生错误!");
				e.printStackTrace();
			}
		}
	}

	/**
	 * 写入表格字段对象
	 * 
	 * @throws Exception
	 */
	//表格中的内容又可能是空的，如果直接操作会出现空指针因此我们可以在操作之前判断一下！
	private void fillFields(WritableSheet wSheet) throws Exception 
	{
		//同样的分别得到表格对象集合，和字段记录集合！
		List fields = getExcelTemplate().getFieldObjct();
		List fieldList = getExcelData().getFieldsList();
		//有多少条记录就循环多少次！
		for (int j = 0; j < fieldList.size(); j++) 
		{
					Dto dataDto = new BaseDto();
					//取出装载记录的集合的当前记录对象！
					Object object = fieldList.get(j);
					//统一记录类型！
					if (object instanceof BasePo) 
					{
						BasePo domain = (BasePo) object;
						dataDto.putAll(domain.toDto());
					} 
					else if (object instanceof BaseVo) 
					{
						BaseVo vo = (BaseVo) object;
						dataDto.putAll(vo.toDto());
					}
					else if (object instanceof BaseDto) 
					{
						Dto dto = (BaseDto) object;
						dataDto.putAll(dto);
					} 
					else 
					{
						log.error(G4Constants.Exception_Head + "不支持的数据类型!");
					}
					//每遍历一条记录就遍历一次字段参数集合，用于填充指定的值！
					for (int i = 0; i < fields.size(); i++) 
					{
						//得到当前表格对象！
						Cell cell = (Cell) fields.get(i);
						//分别得到参数名和参数类型！
						String key = getKey(cell.getContents().trim());
						String type = getType(cell.getContents().trim());
						try 
						{
							if (type.equalsIgnoreCase(G4Constants.ExcelTPL_DataType_Number)) 
							{
								//注意此时的y坐标再不断的加，j代表当前记录的下标，有多少记录j就为几！记录有一个共性就是有统统的标题但是在不同的行！
								Number number = new Number(cell.getColumn(), cell.getRow() + j, dataDto.getAsBigDecimal(key)==null?0.0:dataDto.getAsBigDecimal(key).doubleValue());
								number.setCellFormat(cell.getCellFormat());
								wSheet.addCell(number);
							} 
							else 
							{
								Label label = new Label(cell.getColumn(), cell.getRow() + j, dataDto.getAsString(key));
								label.setCellFormat(cell.getCellFormat());
								wSheet.addCell(label);
							}
						} 
						catch (Exception e) 
						{
							log.error(G4Constants.Exception_Head + "写入表格字段对象发生错误!");
							e.printStackTrace();
						}
					}
		}
		//用来确定统计标题的y轴坐标的
		int row = 0;
		row += fieldList.size();
		//如果没有可导出的数据
		if (G4Utils.isEmpty(fieldList)) 
		{
			System.out.println("记录是空的，因此要将模板中的一些原始数据移除！");
			if (G4Utils.isNotEmpty(fields)) 
			{
				Cell cell = (Cell) fields.get(0);
				//得到字段单元格所在的行！
				row = cell.getRow();
				//如果不移除打印出的回事原始模板数据，因此当没有记录时就必须移除！
				wSheet.removeRow(row+1);
				fillVariables(wSheet,3);
			}
			System.out.println("模板数据移除完毕。。。。。。。。。。。。。。");
		}
		else 
		{
			System.out.println("记录不是空的，因此要开始替换动态变换的值，例如统计总行数！");
			Cell cell = (Cell) fields.get(0);
			//统计的题目的行数变成原来的所在的行加上记录的条数等于现在的位置！
			row += cell.getRow();
			System.out.println("统计标题位于第"+row+"行");
			fillVariables(wSheet, row);
		}
	}

	/**
	 * 写入变量对象
	 */
	private void fillVariables(WritableSheet wSheet, int row) 
	{
		//得到其他变量的集合！
		List variables = getExcelTemplate().getVariableObject();
		Dto parameterDto = getExcelData().getParametersDto();
		//这个又回归到以变量的个数为标准进行遍历覆盖！
		for (int i = 0; i < variables.size(); i++) 
		{
			Cell cell = (Cell) variables.get(i);
			//得到变量的名字和类型！
			String key = getKey(cell.getContents().trim());
			String type = getType(cell.getContents().trim());
			try 
			{
				//根据单元格的数据类型来创建不同的单元格属性！
				if (type.equalsIgnoreCase(G4Constants.ExcelTPL_DataType_Number)) 
				{
					Number number = new Number(cell.getColumn(), row, parameterDto.getAsBigDecimal(key).doubleValue());
					number.setCellFormat(cell.getCellFormat());
					wSheet.addCell(number);
				} 
				else
				{
					String content = parameterDto.getAsString(key);
					//如果表格中内容不是空的并且模板参数名不是nbsp那么就用键面作为值！
					if (G4Utils.isEmpty(content) && !key.equalsIgnoreCase("nbsp")) 
					{
						content = key;
					}
					//如果表格中的内容时nbsp那么就直接使用提供的值来填充！反正都是文本表格！
					Label label = new Label(cell.getColumn(), row, content);
					label.setCellFormat(cell.getCellFormat());
					wSheet.addCell(label);
				}
			} 
			catch (Exception e) 
			{
				log.error(G4Constants.Exception_Head + "写入表格变量对象发生错误!");
				e.printStackTrace();
			}
		}
	}

	/**
	 * 获取模板键名
	 * 
	 * @param pKey
	 *            模板元标记
	 * @return 键名
	 */
	private static String getKey(String pKey) 
	{
		String key = null;
		//得到内容中冒号的下标！
		int index = pKey.indexOf(":");
		//如果下标是-1则代表不存在这个字符！
		if (index == -1)
		{
			//这里用3是有原因的，是要提出特殊符号和打括号，后面的-1也是需要的，否则会将结束的大括号包含进来！
			key = pKey.substring(3, pKey.length() - 1);
		} 
		else 
		{
			key = pKey.substring(3, index);
		}
		return key;
	}

	/**
	 * 获取模板单元格标记数据类型
	 * 
	 * @param pType
	 *            模板元标记
	 * @return 数据类型
	 */
	private static String getType(String pType) 
	{
		String type = G4Constants.ExcelTPL_DataType_Label;
		//如果没有包含冒号n则代表是文本，否则代表是数字！
		if (pType.indexOf(":n") != -1 || pType.indexOf(":N") != -1) 
		{
			type = G4Constants.ExcelTPL_DataType_Number;
		}
		//返回当前单元格数据的类型！
		return type;
	}

	public ExcelTemplate getExcelTemplate() {
		return excelTemplate;
	}

	public void setExcelTemplate(ExcelTemplate excelTemplate) {
		this.excelTemplate = excelTemplate;
	}

	public ExcelData getExcelData() {
		return excelData;
	}

	public void setExcelData(ExcelData excelData) {
		this.excelData = excelData;
	}
}
